import { 
  Component, 
  OnInit, 
  ChangeDetectionStrategy, 
  ComponentFactoryResolver, 
  ChangeDetectorRef, 
  ViewContainerRef, 
  Input, 
  QueryList, 
  ViewChildren, 
  OnDestroy,
  ComponentRef,
  Optional,
  Output,
  EventEmitter} from '@angular/core';
import { GridsterConfig, GridType, DisplayGrid, CompactType } from 'angular-gridster2';
import { merge, append, filter, concat, map, find, propEq, without, mergeDeepRight } from 'ramda';

import { BuilderMainComponent } from '../../builder/builder-main/builder-main.component';
import { Control, ControlType, DashboardType } from '../dashboard-model';
import { HeaderComponent } from '../controls/header/header.component';
import { HrizontalRuleComponent } from '../controls/hrizontal-rule/hrizontal-rule.component';
import { TextComponent } from '../controls/text/text.component';
import { TextAreaComponent } from '../controls/text-area/text-area.component';
import { RadioComponent } from '../controls/radio/radio.component';
import { CheckboxComponent } from '../controls/checkbox/checkbox.component';
import { SwitchComponent } from '../controls/switch/switch.component';
import { DatetimeComponent } from '../controls/datetime/datetime.component';
import { Base } from '../controls/base/base';

@Component({
  selector: 'app-dashboard-main',
  templateUrl: './dashboard-main.component.html',
  styleUrls: ['./dashboard-main.component.scss'],
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class DashboardMainComponent implements OnInit, OnDestroy {

  @Output() itemsChange = new EventEmitter<Control[]>();

  @ViewChildren('dashboardContent', { read: ViewContainerRef })
  contents: QueryList<ViewContainerRef>;

  componentRefs: ControlComponentRef[] = [];

  @Input() set dragging(dragging: Control) {
    if (!dragging) {
      return;
    }

    this._dragging = dragging;
    this.options.defaultItemCols = dragging.minItemCols;
    this.options.defaultItemRows = dragging.minItemRows;
    this.changedOptions();
  };

  _dragging: Control;

  @Input() set completeConfigured(completeConfigured: Control) {
    if (!completeConfigured) {
      return;
    }

    this.setControl(completeConfigured);
  }

  @Input() set items(items: Control[]) {
    if (this._items || !items) {
      return;
    }

    this._items = items;
    this.changeDetectorRef.markForCheck();

    if (!this._items.length) {
      return;
    }

    this._items.forEach((item, index) => {
      setTimeout(() => {
        this.createControl(item, index); 
      });
    })
  };

  _items: Control[];

  @Input() set type(type: DashboardType) {
    if (!type) {
      return;
    }

    this._type = type;
  }

  _type: DashboardType = DashboardType.Renderer;

  options: GridsterConfig;

  constructor(
    public componentFactory: ComponentFactoryResolver,
    public changeDetectorRef: ChangeDetectorRef,
    @Optional() public mainComponent: BuilderMainComponent,
  ) { }

  ngOnInit() {
    let options: GridsterConfig = {
      gridType: GridType.VerticalFixed,
      fixedRowHeight: 44,
      displayGrid: DisplayGrid.None,
      draggable: {
        enabled: false,
      },
      margin: 1,
      outerMargin: true,
      outerMarginTop: 16,
      outerMarginRight: 16,
      outerMarginBottom: 16,
      outerMarginLeft: 16,
      minCols: 12,
      maxCols: 12,
      pushItems: false,
      resizable: {
        enabled: false,
      }
    };

    if (this._type !== DashboardType.Renderer) {
      options = mergeDeepRight(options, {
        displayGrid: DisplayGrid.Always,
        compactType: CompactType.CompactUp,
        draggable: {
          enabled: true,
        },
        enableEmptyCellDrop: true,
        emptyCellDropCallback: this.addControl.bind(this),
        itemChangeCallback: this.changeControl.bind(this),
        pushItems: true,
        pushResizeItems: true,
        pushDirections: {
          north: true,
          east: true,
          south: true,
          west:true
        },
        resizable: {
          enabled: true,
        }
      });
    }

    this.options = options;
  }

  ngOnDestroy() {
    this.resetRef();
  }

  changedOptions() {
    if (this.options.api && this.options.api.optionsChanged) {
      this.options.api.optionsChanged();
      this.options.api.resize();
    }
  }

  resetRef() {
    if (this.componentRefs.length) {
      this.componentRefs.forEach(componentRef => {
        componentRef.ref.destroy();
        this.changeDetectorRef.markForCheck();
      });

      this.componentRefs = [];
    }
  }

  createControl(control: Control, controlIndex: number) {
    this.contents.forEach((content, index) => {

      if (controlIndex !== index) {
        return;
      }

      let component: any;

      switch (control.type) {
        case ControlType.Header:
          component = HeaderComponent;
          break;
        case ControlType.Hr:
          component = HrizontalRuleComponent;
          break;
        case ControlType.Text:
          component = TextComponent;
          break;
        case ControlType.Textarea:
          component = TextAreaComponent;
          break;
        case ControlType.Radio:
          component = RadioComponent;
          break;
        case ControlType.Checkbox:
          component = CheckboxComponent;
          break;
        case ControlType.Switch:
          component = SwitchComponent;
          break;
        case ControlType.Datetime:
          component = DatetimeComponent;
          break;
        default:
          break;
      }
      
      const factory = this.componentFactory.resolveComponentFactory(component);
      const componentRef = content.createComponent(factory);
      this.changeDetectorRef.markForCheck();

      let instance = <Base>componentRef.instance;
      instance.control = control;
      instance.dashboardType = this._type;

      this.addRef(control.uuid, componentRef);
      this.removeControl(instance);
      this.openControlConfig(instance);
    });
  }

  addRef(uuid: string, ref: ComponentRef<any>) {
    const buffer = {
      uuid,
      ref,
    }

    this.componentRefs = concat(this.componentRefs, [buffer]);
  }

  removeRef(control: Control) {
    const traget = find(propEq('uuid', control.uuid))(this.componentRefs)

    traget.ref.destroy();
    this.componentRefs = without([traget], this.componentRefs);
  }

  addControl(event: MouseEvent, control: Control) {
    control = merge(this._dragging, control);
    
    this._items = append(control, this._items);
    this.changeDetectorRef.markForCheck();
    this.itemsChange.emit(this._items);

    setTimeout(() => {
      const index = this._items.length - 1;

      this.createControl(control, index);
    });
  }

  changeControl(control: Control) {
    this.setControl(control);
    this.itemsChange.emit(this._items);
  }
  
  removeControl(instance: Base) {
    instance.remove.subscribe((control: Control) => {
      const isCurrentItem = (x: Control) =>  x.uuid !== control.uuid;

      this._items = filter(isCurrentItem, this._items);
      this.changeDetectorRef.markForCheck();
      this.itemsChange.emit(this._items);
      
      this.removeRef(control);
    });
  }

  setControl(control: Control) {
    this._items = map(item => {
      if (control.uuid === item.uuid) {
        return merge(item, control)
      }

      return item;
    }, this._items);

    this.changeDetectorRef.markForCheck();

    const traget = find(propEq('uuid', control.uuid))(this.componentRefs);

    traget.ref.instance.control = control;
  }

  openControlConfig(instance: Base) {
    instance.openPanel.subscribe((control: Control) => {
      this.mainComponent.configured = control;
      this.mainComponent.openDrawer();
    });
  }
  
  ngForTrackById(index: number, { uuid }: { uuid: string }) {
    return uuid;
  }
}

export interface ControlComponentRef { 
  uuid: string; 
  ref: ComponentRef<any>
}
